package spoon.service;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.transaction.annotation.Transactional;

import spoon.core.domain.Game;
import spoon.core.exception.ConvertException;
import spoon.core.service.HttpService;
import spoon.dao.Bet365Dao;

public abstract class Bet365AbstractService implements Bet365Service {

	@Autowired
	protected HttpService httpService;
	
	@Autowired
	private Bet365Dao bet365Dao;

	@Value("#{bet365['site']}")
	protected String SITE;

	@Value("#{bet365['url']}")
	protected String URL;

	@Value("#{bet365['link']}")
	protected String LINK;

	protected String ITEM_CODE;

	protected String ITEM_NAME;

	protected Map<String, String> leagueMap;

	protected Map<String, String> teamMap;

	protected Logger log = LoggerFactory.getLogger(this.getClass());

	@Override
	public abstract List<Game> getLeagueList();

	@Override
	public abstract void getGame(Game league);

	/**
	 * 토큰에서 key=value 값을 분리해 낸다.
	 * 
	 * @param token
	 * @param key
	 * @return
	 */
	protected String getTokenValue(String token, String key) {
		String value = "";
		Pattern p = Pattern.compile(key + "=([^;]+);");
		Matcher m = p.matcher(token);
		if (m.find()) {
			value = m.group(1).trim();
		}

		return value;
	}

	/**
	 * 리그정보에서 각 개별 token의 List를 가져온다.
	 * 
	 * @param link
	 * @return
	 */
	protected List<String> getTokenValues(String source, String token) {
		Pattern p = Pattern.compile(token + "=([^;]+);");
		Matcher m = p.matcher(source);
		List<String> list = new ArrayList<String>();

		while (m.find()) {
			list.add(m.group(1));
		}
		return list;
	}	
	
	/**
	 * 소스에서 라인을 뽑아 온다.
	 * 
	 * @param source
	 * @param key
	 * @return
	 */
	protected String getTokenLine(String source, String key) {
		String value = "";
		Pattern p = Pattern.compile("(" + key + ";([^\\|]+)\\|");
		Matcher m = p.matcher(source);
		if (m.find()) {
			value = m.group(1);
		}

		return value;
	}
	
	/**
	 * 소스에서 뽑은 모든 라인을 리스트로 넘겨 준다.
	 * 
	 * @param source
	 * @param key
	 * @return
	 */
	protected List<String> getTokenLines(String source, String key) {
		Pattern p = Pattern.compile("(" + key + ";[^\\|]+)\\|");
		Matcher m = p.matcher(source);
		List<String> list = new ArrayList<String>();
		while (m.find()) {
			list.add(m.group(1));
		}

		return list;
	}

	/**
	 * 변환되지 않은 링크정보를 가지고 Full League 링크를 가져온다.
	 * 
	 * @param link
	 * @return
	 */
	protected String getLeagueLink(String link) {
		String source = httpService.getSource(convertLink(link));
		return getTokenValue(source, "PD");
	}

	/**
	 * 소스를 start시작을 중심으로 token 값의 value를 리턴한다.
	 * 
	 * @param source
	 * @param start
	 * @param name
	 * @return
	 */
	protected String getBettingLink(String source, String start, String name) {
		Pattern p = Pattern.compile(start + "[^|]+NA=" + name.replaceAll(" ", "\\\\s") + "[^|]+PD=([^;]+);");
		Matcher m = p.matcher(source);
		String link = "";
		if (m.find()) {
			link = m.group(1);
		}
		return link;
	}

	/**
	 * source에서 매칭하여 리스트로 리턴한다.
	 * 
	 * @param source
	 * @param start
	 * @param token
	 * @return
	 */
	protected List<String> getBettingInfo(String source, String start, String token) {
		Pattern p = Pattern.compile(start + "[^|]+" + token + "=([^;]+);");
		Matcher m = p.matcher(source);
		List<String> list = new ArrayList<String>();
		while (m.find()) {
			list.add(m.group(1));
		}

		return list;
	}

	/**
	 * 링크 파리메타를 받아서 전체 주소로 변환한다.
	 * 
	 * @param link
	 * @return
	 */
	protected String convertLink(String link) {
		return LINK + link.replaceAll("#", "%23");
	}

	/**
	 * 리그코드 변경
	 * 
	 * @param league
	 * @return
	 */
	protected String getLeagueCode(String league) {
		String leagueCode = leagueMap.get(league);
		if (leagueCode == null || "".equals(leagueCode)) {
			leagueCode = league;
			//log.error("{} {} 리그코드 변경에러, 리그명 = {}", SITE, ITEM_NAME, league);
		}
		return leagueCode;
	}

	/**
	 * 팀코드 변경
	 * 
	 * @param team
	 * @return
	 */
	protected String getTeamCode(String team) {
		String teamCode = teamMap.get(team);
		if (teamCode == null || "".equals(teamCode)) {
			teamCode = team;
			//log.error("{} {} 팀코드 변경에러, 팀명 = {}", SITE, ITEM_NAME, team);
		}
		return teamCode;
	}

	/**
	 * 날짜 변환 표준시간으로 변환
	 * 
	 * @param dt
	 * @return
	 */
	protected Date convertDate(String dt) {
		DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
		TimeZone tz = TimeZone.getTimeZone("Greenwich");
		df.setTimeZone(tz);

		Date date = new Date();
		try {
			date = df.parse(dt);
		} catch (ParseException e) {
			throw new ConvertException(new Throwable(SITE + " " + ITEM_NAME + " 날짜변경 에러 (" + dt + ")", e));
		}
		return date;
	}

	/**
	 * 배당변환 분수 형태를 소수점 형태로
	 * 
	 * @param rate
	 * @return
	 */
	protected Double convertRate(String rate) {
		if (rate == null || rate.equals("0")) {
			rate = "0/1";
		}
		String[] s = rate.split("/");

		if (s.length != 2) {
			throw new ConvertException(new Throwable(SITE + " " + ITEM_NAME + " 배당율 변경 에러 (" + rate + ")"));
		}
		Double d = 1D + (Double.parseDouble(s[0]) / Double.parseDouble(s[1]));
		return Double.parseDouble(String.format("%.2f", d - 0.005));
	}

	/**
	 * 핸디캡 변환
	 * 
	 * @param handicap
	 * @return
	 */
	protected Double convertHandicap(String handicap) {
		Double d = null;
		
		if (handicap == null || "".equals(handicap)) {
			d = 0D;
		} else if (handicap.contains(",")) {
			String[] hs = handicap.split(",");
			d = (Double.parseDouble(hs[0]) + Double.parseDouble(hs[1])) / 2D;
		} else {
			d = Double.parseDouble(handicap);
		}

		return d;
	}

	/**
	 * 핸디캡을 기호를 포함한 스트링으로 변환한다.
	 * 
	 * @param handicap
	 * @return
	 */
	protected String convertHandiString(String handicap) {
		return convertHandiString(convertHandicap(handicap));
	}

	/**
	 * 핸디캡을 기호를 포함한 스트링으로 변환한다.
	 * 
	 * @param handicap
	 * @return
	 */
	protected String convertHandiString(Double handicap) {
		if (handicap == 0) {
			return "0";
		} else if (handicap > 0) {
			return "+" + handicap;
		} else {
			return handicap.toString();
		}
	}

	/**
	 * 더블찬스
	 * 
	 * @param game
	 * @param link
	 */
	protected void betDoubleChance(Game game, String link, Map<String, String> map) {
		if (link == null || "".equals(link))
			return;

		String source = httpService.getSource(convertLink(link));
		List<String> lines = getTokenLines(source, "PA");
		
		Game betting;
		
		for (String line : lines) {
			String name = getTokenValue(line, "NA");
			String rate = getTokenValue(line, "OD");
			
			betting = new Game();
			
			betting.setBetTarget(map.get("betTarget"));
			betting.setBetTime(map.get("betTime"));
			betting.setBetType(map.get("betType"));
			betting.setBetName(map.get("betName"));
			betting.setHandicap(0D);
			betting.setHandicapName("");
			betting.setBetRate(convertRate(rate));
			betting.setBetRateName(name);
			
			if (name.startsWith("Draw")) { // X2
				betting.setBetRateCode("X2");
			} else if (name.endsWith("Draw")) { // 1X
				betting.setBetRateCode("1X");
			} else { // 12
				betting.setBetRateCode("12");
			}
			game.addGameList(betting);

		}
	}
	
	/**
	 * 오버/언더 (Total Goals)
	 */
	protected void betOverUnder(Game game, String link, Map<String, String> map) {
		if (link == null || "".equals(link))
			return;
		
		String source = httpService.getSource(convertLink(link));
		List<String> lines = getTokenLines(source, "PA");
		
		Game betting;
		
		for (String line : lines) {
			String name = getTokenValue(line, "NA");
			String rate = getTokenValue(line, "OD");
			String handi = getTokenValue(line, "HA");
			String handiString = getTokenValue(line, "HD");
			
			betting = new Game();
			
			betting.setBetTarget(map.get("betTarget"));
			betting.setBetTime(map.get("betTime"));
			betting.setBetType(map.get("betType"));
			betting.setBetName(map.get("betName"));
			betting.setHandicap(convertHandicap(handi));
			betting.setHandicapName(handiString);
			betting.setBetRate(convertRate(rate));
			betting.setBetRateName(name);
			betting.setBetRateCode(handiString);

			game.addGameList(betting);
		}

	}
	
	/**
	 * 핸디캡 (Asian Handicap)
	 */
	protected void betHandicap(Game game, String link, Map<String, String> map) {
		if (link == null || "".equals(link))
			return;
		
		String source = httpService.getSource(convertLink(link));
		List<String> lines = getTokenLines(source, "PA");
		
		Game betting;
		
		for (String line : lines) {
			String name = getTokenValue(line, "NA");
			String rate = getTokenValue(line, "OD");
			String handi = getTokenValue(line, "HA");
			String handiString = getTokenValue(line, "HD");
			
			betting = new Game();
			
			betting.setBetTarget(map.get("betTarget"));
			betting.setBetTime(map.get("betTime"));
			betting.setBetType(map.get("betType"));
			betting.setBetName(map.get("betName"));
			betting.setHandicap(convertHandicap(handi));
			betting.setHandicapName(handiString);
			betting.setBetRate(convertRate(rate));
			betting.setBetRateName(name);
			if (game.getHomeTeamName().equals(name)) {
				betting.setBetRateCode("1");
			} else if (game.getAwayTeamName().equals(name)) {
				betting.setBetRateCode("2");
			} else {
				betting.setBetRateCode("X");
			}
			game.addGameList(betting);
		}

	}
	
	/**
	 * 디비 입력
	 * 
	 * @param league
	 * @return
	 */
	@Transactional
	protected int addBetting(Game league) {
		
		bet365Dao.delete(league);
		return bet365Dao.addBetting(league);
		
	}

}
